using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Microsoft.DirectX;

namespace Boids2D
{
    class World
    {
        Random rand = new Random();


        int width;
        int height;

        List<Boid> boidList;
        const int NUM_BOIDS = 200;
        const float MAX_ACCEL = .5f;
        const float MAX_VEL = 3f;





        double senseDistance;
        double senseAngle = .6 * Math.PI;
        bool avoidingLocation;
        Vector2 avoidLoc;
        int avoidScaler;
        int moveToCenterScaler;
        int keepAwayScaler;
        int matchVelocityScaler;

        #region Properties
        public double SenseAngle
        {
            get { return senseAngle; }
            set { senseAngle = value; }
        }
        public double SenseDistance
        {
            get { return senseDistance; }
            set { senseDistance = value; }
        }
        public int AvoidScaler
        {
            get { return avoidScaler; }
            set { avoidScaler = value; }
        }
        public int MoveToCenterScaler
        {
            get { return moveToCenterScaler; }
            set { moveToCenterScaler = value; }
        }
        public int KeepAwayScaler
        {
            get { return keepAwayScaler; }
            set { keepAwayScaler = value; }
        }
        public int MatchVelocityScaler
        {
            get { return matchVelocityScaler; }
            set { matchVelocityScaler = value; }
        }
        public bool AvoidingLocation
        {
            get { return avoidingLocation; }
            set { avoidingLocation = value; }
        }
        public Vector2 AvoidLoc
        {
            get { return avoidLoc; }
            set { avoidLoc = value; }
        }
        #endregion


        public World(int widthIn, int heightIn)
        {
            avoidLoc = new Vector2();
            avoidingLocation = false;

            width = widthIn;
            height = heightIn;

            boidList = new List<Boid>();
            for (int i = 0; i < NUM_BOIDS; i++)
            {
                Boid myBoid = new Boid();
                myBoid.Position = new Vector2(width / 2, height / 2);
                myBoid.Velocity = new Vector2(rand.Next(10) - 5, rand.Next(10) - 5);
                boidList.Add(myBoid);
            }


        }


        internal void Draw(Microsoft.DirectX.Direct3D.Device device)
        {
            foreach (Boid myBoid in boidList)
            {
                myBoid.Draw(device);

            }
            device.Present();
        }

        internal void UpdateWorld()
        {
            //Vector2 centerOfMass = GetCenterOfMass(boidList);
            //Vector2 aveVelocity = GetAverageVelocity(boidList);
            foreach (Boid myBoid in boidList)
            {
                UpdateBoid(myBoid);//, centerOfMass, aveVelocity);
            }
        }

        private void UpdateBoid(Boid myBoid, Vector2 centerOfMass, Vector2 aveVelocity)
        {
            Vector2 rule1 = Rule1MoveToCenterOfMass(myBoid, centerOfMass);
            rule1.Multiply(moveToCenterScaler / 100.0f);
            Vector2 rule2 = Rule2AvoidBoids(myBoid);
            rule2.Multiply(keepAwayScaler / 100.0f);
            Vector2 rule3 = Rule3MatchVelocity(myBoid, aveVelocity);
            rule3.Multiply(matchVelocityScaler / 100.0f);


            myBoid.Velocity = Vector2.Add(myBoid.Velocity, rule1);
            myBoid.Velocity = Vector2.Add(myBoid.Velocity, rule2);
            myBoid.Velocity = Vector2.Add(myBoid.Velocity, rule3);

            if (avoidingLocation)
            {
                Vector2 avoid = AvoidLocation(avoidLoc, myBoid);
                avoid.Multiply(avoidScaler / -100.0f);
                myBoid.Velocity = Vector2.Add(myBoid.Velocity, avoid);
            }


            //limit speed

            //double mag = myBoid.Velocity.Length();
            if (myBoid.Velocity.Length() > MAX_VEL)
            {
                // myBoid.Velocity = myBoid.Velocity.Multiply(1 / mag).Multiply(MAX_VEL);
                Vector2 temp = Vector2.Normalize(myBoid.Velocity);
                myBoid.Velocity = Vector2.Multiply(temp, MAX_VEL);
            }


            myBoid.Position = Vector2.Add(myBoid.Position, myBoid.Velocity);

            ConfineBoid(myBoid, 0);
        }

        private void UpdateBoid(Boid myBoid)
        {
            List<Boid> nearBoidList = FindSensedBoids(myBoid);

            if (nearBoidList.Count > 0)
            {
                Vector2 centerOfMass1 = GetCenterOfMass(nearBoidList);
                Vector2 rule1 = Rule1MoveToCenterOfMass(myBoid, centerOfMass1);
                rule1.Multiply(moveToCenterScaler / 100.0f);
                myBoid.Velocity = Vector2.Add(myBoid.Velocity, rule1);


                Vector2 aveVelocity1 = GetAverageVelocity(nearBoidList);
                Vector2 rule3 = Rule3MatchVelocity(myBoid, aveVelocity1);
                rule3.Multiply(matchVelocityScaler / 100.0f);
                myBoid.Velocity = Vector2.Add(myBoid.Velocity, rule3);
            }

            Vector2 rule2 = Rule2AvoidBoids(myBoid);
            rule2.Multiply(1 / 100.0f);
            myBoid.Velocity = Vector2.Add(myBoid.Velocity, rule2);

            if (avoidingLocation)
            {
                Vector2 avoid = AvoidLocation(avoidLoc, myBoid);
                avoid.Multiply(avoidScaler / -100.0f);
                myBoid.Velocity = Vector2.Add(myBoid.Velocity, avoid);
            }


            //limit speed

            //double mag = myBoid.Velocity.Length();
            if (myBoid.Velocity.Length() > MAX_VEL)
            {
                // myBoid.Velocity = myBoid.Velocity.Multiply(1 / mag).Multiply(MAX_VEL);
                Vector2 temp = Vector2.Normalize(myBoid.Velocity);
                myBoid.Velocity = Vector2.Multiply(temp, MAX_VEL);
            }

            myBoid.Position = Vector2.Add(myBoid.Position, myBoid.Velocity);
            ConfineBoid(myBoid, 1);

        }

        private void ConfineBoid(Boid myBoid, int type)
        {
            if (type == 1)
            {
                float scale = .3f;
                //soft Walls
                if (myBoid.Position.X < 0)
                {
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(scale, 0f));
                }
                if (myBoid.Position.Y < 0)
                {
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(0f, scale));
                }
                if (myBoid.Position.X > width)
                {
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(-scale, 0f));

                }
                if (myBoid.Position.Y > height)
                {
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(0f, -scale));
                }
            }
            else
            {
                //Hard walls
                if (myBoid.Position.X < 0)
                {
                    //myBoid.Position.X = 0;
                    //myBoid.Velocity.X = -myBoid.Velocity.X * .75;
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(myBoid.Velocity.X, 0f));
                }
                if (myBoid.Position.Y < 0)
                {
                    //myBoid.Position.Y = 0;
                    //myBoid.Velocity.Y = -myBoid.Velocity.Y * .75;
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(0f, myBoid.Velocity.Y));
                }
                if (myBoid.Position.X > width)
                {
                    //myBoid.Position.X = width;
                    //myBoid.Velocity.X = -myBoid.Velocity.X * .75;
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(-myBoid.Velocity.X, 0f));
                }
                if (myBoid.Position.Y > height)
                {
                    //myBoid.Position.Y = height;
                    //myBoid.Velocity.Y = -myBoid.Velocity.Y * .75;
                    myBoid.Velocity = Vector2.Add(myBoid.Velocity, new Vector2(0f, -myBoid.Velocity.Y));
                }
            }

        }


        private List<Boid> FindSensedBoids(Boid myBoid)
        {
            List<Boid> tempList = new List<Boid>();
            foreach (Boid boid in boidList)
            {
                if (boid != myBoid)
                {
                    Vector2 diff = Vector2.Subtract(boid.Position, myBoid.Position);
                    if (diff.Length() < senseDistance)
                    {
                        double theta = GetAngleBetween(myBoid.Velocity, diff);
                        if (theta <= senseAngle)
                        {
                            tempList.Add(boid);
                        }
                    }

                }
            }
            return tempList;
        }

        private double GetAngleBetween(Vector2 vec1, Vector2 vec2)
        {
            vec1.Normalize();
            vec2.Normalize();
            double angle = Math.Acos((double)Vector2.Dot(vec1, vec2));

            return angle;
        }

        private Vector2 GetCenterOfMass(List<Boid> listIn)
        {

            Vector2 centerOfMass1 = new Vector2();

            foreach (Boid boid in listIn)
            {
                centerOfMass1.Add(boid.Position);
            }
            int numBoids = listIn.Count;
            centerOfMass1.Multiply(1.0f / (numBoids));

            return centerOfMass1;
        }
        private Vector2 GetAverageVelocity(List<Boid> listIn)
        {
            Vector2 newVelocity = new Vector2();
            foreach (Boid boid in listIn)
            {
                newVelocity.Add(boid.Velocity);
            }
            int numBoids = boidList.Count;
            newVelocity.Multiply(1.0f / numBoids);
            return newVelocity;
        }

        private Vector2 Rule1MoveToCenterOfMass(Boid boidIn, Vector2 centerIn)
        {
            //Rule 1: Boids try to fly towards the centre of mass of neighbouring boids. 
            /*
            PROCEDURE rule1(boid bIn)

	            Vector2 pcJ

	            FOR EACH BOID b
		            IF b != bIn THEN
			            pcJ = pcJ + b.position
		            END IF
	            END

	            pcJ = pcJ / N-1

	            RETURN (pcJ - bIn.position) / 100

            END PROCEDURE
            */
            Vector2 result = new Vector2();


            float scaler = 1.0f / 100f;
            result = Vector2.Subtract(centerIn, boidIn.Position);
            result.Multiply(scaler);
            return result;

        }

        private Vector2 Rule2AvoidBoids(Boid boidIn)
        {
            //Rule 2: Boids try to keep a small distance away from other objects (including other boids). 
            /*
                    PROCEDURE rule2(boid bJ)

                        Vector2 c = 0;

                        FOR EACH BOID b
                            IF b != bJ THEN
                                IF |b.position - bJ.position| < 100 THEN
                                    c = c - (b.position - bJ.position)
                                END IF
                            END IF
                        END

                        RETURN c

                    END PROCEDURE
            */

            Vector2 result = new Vector2();
            double maxAvoidDist = keepAwayScaler;

            foreach (Boid boid in boidList)
            {
                if (boidIn != boid)
                {
                    Vector2 temp = Vector2.Subtract(boid.Position, boidIn.Position);
                    double dist = temp.Length();
                    if (dist < maxAvoidDist)
                    {
                        result.Subtract(temp);
                    }
                }
            }



            return result;
        }

        private Vector2 Rule3MatchVelocity(Boid boidIn, Vector2 averageVelocityIn)
        {
            //Rule 3: Boids try to match velocity with near boids. 
            /*
            PROCEDURE rule3(boid bJ)

		        Vector2 pvJ

		        FOR EACH BOID b
			        IF b != bJ THEN
				        pvJ = pvJ + b.velocity
			        END IF
		        END

		        pvJ = pvJ / N-1

		        RETURN (pvJ - bJ.velocity) / 8

	        END PROCEDURE
            */
            Vector2 result;
            float scaler = 1.0f / 8f;

            result = Vector2.Subtract(averageVelocityIn, boidIn.Velocity);
            result.Multiply(scaler);

            return result;
        }

        private Vector2 AvoidLocation(Vector2 locationIn, Boid boidIn)
        {
            /*           //Tendency towards a particular place
            PROCEDURE tend_to_place(Boid b)
                Vector2 place

                RETURN (place - b.position) / 100
            END PROCEDURE
        
             */

            Vector2 temp = Vector2.Subtract(locationIn, boidIn.Position);
            temp.Multiply(.01f);
            temp.Multiply(1.0f / (float)Math.Pow(Vector2.Length(temp), 3));
            return temp;
        }

    }
}
